/*
 * Journal.java
 *
 * Created on April 12, 2007, 4:11 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.app.db;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Iterator;
import java.util.UUID;

/**
 *
 * @author alex
 */
public class Journal extends DBObject
{
   
   public static final int DELETE_OPERATION = 1;
   public static final int CREATE_OPERATION = 2;
   public static final int MODIFY_OPERATION = 4;
   
   public class Entry {
      int id;
      int operation;
      Date occurred;
      Entry(int id, Date occurred,int operation) {
         this.id = id;
         this.operation = operation;
         this.occurred = occurred;
      }
      
      public int getId() {
         return id;
      }
      
      public int getOperation() {
         return operation;
      }
      
      public Date getOccurredOn() {
         return occurred;
      }
   }
   
   public class DeleteEntry extends Entry {
      UUID feed;
      UUID entry;
      String path;
      DeleteEntry(int id,Date occurred,UUID feed, String path,UUID entry)
      {
         super(id,occurred,DELETE_OPERATION);
         this.feed = feed;
         this.path = path;
         this.entry = entry;
      }
      public UUID getFeed() {
         return feed;
      }
      public String getPath() {
         return path;
      }
      public UUID getEntry() {
         return entry;
      }
   }
   
   public class UpdatedEntry extends Entry {
      int feedId;
      int entryId;
      int resourceId;
      UpdatedEntry(int id,Date occurred,int operation,int feedId,int entryId,int resourceId)
      {
         super(id,occurred,operation);
         this.feedId = feedId;
         this.entryId = entryId;
         this.resourceId = resourceId;
      }
      
      public int getFeedId() {
         return feedId;
      }
      
      public Feed getFeed() 
         throws SQLException
      {
         return db.getFeed(feedId);
      }
      
      public int getEntryId() {
         return entryId;
      }
      
      public org.atomojo.app.db.Entry getEntry() 
         throws SQLException
      {
         return entryId<0 ? null : db.getEntry(entryId);
      }
      
      public int getResourceId() {
         return resourceId;
      }
      
      public EntryMedia getResource() 
         throws SQLException
      {
         return resourceId<0 ? null : db.getResource(resourceId);
      }
   }
   
   /** Creates a new instance of Journal */
   public Journal(DB db)
   {
      super(db);
   }
   
   public void recordCreate(int feedId) 
      throws SQLException
   {
      journal(CREATE_OPERATION,feedId,-1,-1);
   }
   public void recordCreate(int feedId,int entryId) 
      throws SQLException
   {
      journal(CREATE_OPERATION,feedId,entryId,-1);
   }
   public void recordModify(int feedId) 
      throws SQLException
   {
      journal(MODIFY_OPERATION,feedId,-1,-1);
   }
   
   public void recordModify(int feedId,int entryId) 
      throws SQLException
   {
      journal(MODIFY_OPERATION,feedId,entryId,-1);
   }
   public void recordModify(int feedId,int entryId,int resourceId) 
      throws SQLException
   {
      journal(MODIFY_OPERATION,feedId,entryId,resourceId);
   }
   
   public void truncate() 
      throws SQLException
   {
      truncate(new Timestamp((new Date()).getTime()));
   }
   
   public void truncate(Timestamp start) 
      throws SQLException
   {
      
      // Remove deletes
      synchronized (this) {
         DBConnection dbConnection = db.getConnection();
         PreparedStatement s = null;
         try {
            try {
               s = dbConnection.getStatement(DB.STATEMENT_TRUNCATE_DELETE_JOURNAL);
               s.setTimestamp(1,start);
               s.executeUpdate();
            } finally {
               db.release(s);
               s = null;
            }
            try {
               s = dbConnection.getStatement(DB.STATEMENT_TRUNCATE_UPDATE_JOURNAL);
               s.setTimestamp(1,start);
               s.executeUpdate();
            } finally {
               db.release(s);
            }
         } finally {
            db.release(dbConnection);
         }
      }
      
   }
   
   public void recordDelete(Feed feed)
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      try {
         PreparedStatement s = null;

         // Remove entry deletes for feed that is being deleted
         synchronized (this) {
            try {
               s = dbConnection.getStatement(DB.STATEMENT_DELETE_FEED_DELETE_JOURNAL);
               s.setString(1,feed.getUUID().toString());
               s.executeUpdate();
            } finally {
               db.release(s);
               s = null;
            }
         }

         // Remove update entries for the feed
         synchronized (this) {
            try {
               s = dbConnection.getStatement(DB.STATEMENT_DELETE_FEED_UPDATE_JOURNAL);
               s.setInt(1,feed.getId());
               s.executeUpdate();
            } finally {
               db.release(s);
               s = null;
            }
         }

         // Insert the delete for the feed
         Timestamp tstamp = new Timestamp((new Date()).getTime());
         try {
            s = dbConnection.getStatement(DB.STATEMENT_CREATE_FEED_DELETE_JOURNAL);
            s.setTimestamp(1,tstamp);
            s.setString(2,feed.getUUID().toString());
            s.setString(3,feed.getPath());
            s.executeUpdate();
         } finally {
            db.release(s);
         }
      } finally {
         db.release(dbConnection);
      }
   }
   
   public void recordDelete(org.atomojo.app.db.Entry entry)
      throws SQLException
   {
      Feed feed = entry.getFeed();
      DBConnection dbConnection = db.getConnection();
      try {
         Timestamp tstamp = new Timestamp((new Date()).getTime());
         PreparedStatement s = null;
         try {
            s = dbConnection.getStatement(DB.STATEMENT_CREATE_ENTRY_DELETE_JOURNAL);
            s.setTimestamp(1,tstamp);
            s.setString(2,feed.getUUID().toString());
            s.setString(3,feed.getPath());
            s.setString(4,entry.getUUID().toString());
            s.executeUpdate();
         } finally {
            db.release(s);
            s = null;
         }
         // Remove updates for this entry
         synchronized (this) {
            try {
               s = dbConnection.getStatement(DB.STATEMENT_DELETE_ENTRY_UPDATE_JOURNAL);
               s.setInt(1,entry.getId());
               s.executeUpdate();
            } finally {
               db.release(s);
               s = null;
            }
         }
      } finally {
         db.release(dbConnection);
      }
   }
   
   protected void journal(int operation, int feedId,int entryId,int resourceId) 
      throws SQLException
   {
      synchronized (this) {
         DBConnection dbConnection = db.getConnection();
         Timestamp tstamp = new Timestamp((new Date()).getTime());
         try {
            if ((operation & MODIFY_OPERATION)>0) {
               if (entryId<0) {
                  PreparedStatement s = null;
                  try {
                     s = dbConnection.getStatement(DB.STATEMENT_DELETE_MODIFY_FEED_UPDATE_JOURNAL);
                     s.setInt(1,feedId);
                     s.executeUpdate();
                  } finally {
                     db.release(s);
                  }
               } else if (resourceId<0) {
                  PreparedStatement s = null;
                  try {
                     s = dbConnection.getStatement(DB.STATEMENT_DELETE_MODIFY_ENTRY_UPDATE_JOURNAL);
                     s.setInt(1,feedId);
                     s.setInt(2,entryId);
                     s.executeUpdate();
                  } finally {
                     db.release(s);
                  }
               } else {
                  PreparedStatement s = null;
                  try {
                     s = dbConnection.getStatement(DB.STATEMENT_DELETE_MODIFY_RESOURCE_UPDATE_JOURNAL);
                     s.setInt(1,feedId);
                     s.setInt(2,entryId);
                     s.setInt(3,resourceId);
                     s.executeUpdate();
                  } finally {
                     db.release(s);
                  }
               }
            }
            if ((operation & MODIFY_OPERATION)>0 || (operation & CREATE_OPERATION)>0) {
               if (entryId<0) {
                  PreparedStatement s = null;
                  try {
                     s = dbConnection.getStatement(DB.STATEMENT_CREATE_MODIFY_FEED_UPDATE_JOURNAL);
                     s.setTimestamp(1,tstamp);
                     s.setInt(2,operation);
                     s.setInt(3,feedId);
                     s.executeUpdate();
                  } finally {
                     db.release(s);
                  }
               } else if (resourceId<0) {
                  PreparedStatement s = null;
                  try {
                     s = dbConnection.getStatement(DB.STATEMENT_CREATE_MODIFY_ENTRY_UPDATE_JOURNAL);
                     s.setTimestamp(1,tstamp);
                     s.setInt(2,operation);
                     s.setInt(3,feedId);
                     s.setInt(4,entryId);
                     s.executeUpdate();
                  } finally {
                     db.release(s);
                  }
               } else {
                  PreparedStatement s = null;
                  try {
                     s = dbConnection.getStatement(DB.STATEMENT_CREATE_MODIFY_RESOURCE_UPDATE_JOURNAL);
                     s.setTimestamp(1,tstamp);
                     s.setInt(2,operation);
                     s.setInt(3,feedId);
                     s.setInt(4,entryId);
                     s.setInt(5,resourceId);
                     s.executeUpdate();
                  } finally {
                     db.release(s);
                  }
               }
            }
         } finally {
            db.release(dbConnection);
         }
      }
      
   }
   
   public Iterator<Entry> getUpdates()
      throws SQLException
   {
      return getUpdatesSince(null,new Date());
   }
   
   public Iterator<Entry> getDeletes()
      throws SQLException
   {
      return getDeletesSince(null,new Date());
   }
   
   public Iterator<Entry> getDeletesSince(Date since, Date before)
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      try {
         ResultSet r = null;
         if (since==null) {
            Timestamp beforeTstamp = new Timestamp(before.getTime());
            PreparedStatement s = dbConnection.getStatement(DB.STATEMENT_DELETES_BEFORE);
            s.setTimestamp(1,beforeTstamp);
            r = s.executeQuery();
         } else {
            Timestamp sinceTstamp = new Timestamp(since.getTime());
            Timestamp beforeTstamp = new Timestamp(before.getTime());
            PreparedStatement s = dbConnection.getStatement(DB.STATEMENT_DELETES_BETWEEN);
            s.setTimestamp(1,sinceTstamp);
            s.setTimestamp(2,beforeTstamp);
            r = s.executeQuery();
         }
         return new DBIterator<Entry>(r,new ResultConstructor<Entry>() {
            public Entry newInstance(ResultSet set)
               throws SQLException
            {
               String feedIdS = set.getString(3);
               String entryIdS = set.getString(5);
               return new DeleteEntry(set.getInt(1),set.getTimestamp(2),UUID.fromString(feedIdS),set.getString(4),entryIdS==null ? null : UUID.fromString(entryIdS));
            }
         },db,dbConnection);
      } catch (SQLException ex) {
         db.release(dbConnection);
         throw ex;
      }
   }
   public Iterator<Entry> getUpdatesSince(Date since, Date before)
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      try {
         ResultSet r = null;
         if (since==null) {
            Timestamp beforeTstamp = new Timestamp(before.getTime());
            PreparedStatement s = dbConnection.getStatement(DB.STATEMENT_UPDATES_BEFORE);
            s.setTimestamp(1,beforeTstamp);
            r = s.executeQuery();
         } else {
            Timestamp sinceTstamp = new Timestamp(since.getTime());
            Timestamp beforeTstamp = new Timestamp(before.getTime());
            PreparedStatement s = dbConnection.getStatement(DB.STATEMENT_UPDATES_BETWEEN);
            s.setTimestamp(1,sinceTstamp);
            s.setTimestamp(2,beforeTstamp);
            r = s.executeQuery();
         }
         return new DBIterator<Entry>(r,new ResultConstructor<Entry>() {
            public Entry newInstance(ResultSet set)
               throws SQLException
            {
               int feedId = set.getInt(4);
               int entryId = set.getInt(5);
               int resourceId = set.getInt(6);
               if (entryId==0) { entryId = -1; }
               if (resourceId==0) { resourceId = -1; }
               return new UpdatedEntry(set.getInt(1),set.getTimestamp(2),set.getInt(3),feedId,entryId,resourceId);
            }
         },db,dbConnection);
      } catch (SQLException ex) {
         db.release(dbConnection);
         throw ex;
      }
   }
}
